// locutor
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: GM.scala 86 2010-05-11 21:32:13Z normen.mueller $
package locutor.rws.gm

import java.io.File

import scala.xml.{Node, Elem, Text}

import scaup.Views._

import locutor.rws.gm.parsing._

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
abstract class AbstractIdentifier

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class Identifier(val name: String) extends AbstractIdentifier {
  override def toString = toGrGen
  
  def toGrGen = name
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class AnnotatedIdentifier(val name: String, val annos: Option[Map[String, Option[String]]]) extends AbstractIdentifier {
  override def toString = toGrGen
  
  def toGrGen = name + (annos match {
    case Some(as) => "[" + ((as map (_ match {case (key, Some(value)) => key+"="+value case (key, None) => key})) mkString ",") + "]"
    case None => ""
  })
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class RangedIdentifier(val name: String, val range: Option[String]) extends AbstractIdentifier {
  override def toString = toGrGen
  
  def toGrGen = name + (range match {case Some(range) => range case _ => ""})
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
abstract class GraphType

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class EnumType(val name: AnnotatedIdentifier, val entries: Option[Map[AnnotatedIdentifier, Option[String]]]) extends GraphType {
  override def toString = toGrGen
  
  def toGrGen = "enum " + name + "{" + (entries match { 
    case Some(es) => (es map (_ match {case (key, Some(value)) => key+"="+value case (key, None) => key})) mkString ","
    case None     => "" 
  }) + "}"
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class NodeType(val abst: Boolean, val cons: Boolean, val id: AnnotatedIdentifier, val exts: List[Identifier], val atts: List[Attribute]) extends GraphType {  
  override def toString = toGrGen
  
  def toGrGen =
    ( (if(abst) "abstract " else "")
    + (if(cons) "const " else "")
    + "node class " + id
    + (exts match {case Nil => "" case _ => " extends " + (exts mkString ",") }) 
    + (atts match {case Nil => ";" case _ => " {" + (atts mkString ("", ";", ";")) + "}" })
    )
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class EdgeType(val abst: Boolean, val cons: Boolean, val dir: Option[String], val id: AnnotatedIdentifier, val exts: List[Identifier], val asss: List[Assertion], val atts: List[Attribute]) extends GraphType {
  override def toString = toGrGen
  
  def toGrGen =
    ( (if(abst) "abstract" else "")
    + (if(cons) "const" else "")
    + (dir match {case Some(d) => d case _ => ""})
    + "edge class " + id
    + (exts match {case Nil => "" case _  => " extends " + (exts mkString ",") })
    + (asss match {case Nil => "" case _  => " connect " + (asss mkString ",") })
    + (atts match {case Nil => ";" case _ => " {" + (atts mkString ("", ";", ";")) + "}" })
    )  
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
abstract class Assertion

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case object ImplicitAssertion extends Assertion {
  override def toString = toGrGen
  
  def toGrGen = "copy extends"
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class ExplicitAssertion(domain: RangedIdentifier, dir: String, codomain: RangedIdentifier) extends Assertion {
  override def toString = toGrGen
  
  def toGrGen = domain + dir + codomain
}

/**
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
case class Attribute(val key: AnnotatedIdentifier, val typ: Option[String], val value: Option[String]) {
  override def toString = toGrGen
  
  def toGrGen = key + (typ match {case Some(t) => ":"+t case _ => ""}) + (value match {case Some(v) => " = "+v case _ => ""})
}

/** Companion to <code>GraphModel</code> class.
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
object GraphModel {
  private [gm] val GRAPH_MODELS = System.getProperty("locutor.kb").toFile / "rws" / "gm"
  
  def empty = new GraphModel(Nil, Nil, Nil)
  
  private [rws] def parse(spec: File): GraphModel =
    if(! ((GRAPH_MODELS / spec.path) exists))
      error("Graph model \"" + (GRAPH_MODELS / spec.path).path + "\" does not exist.")
    else parse((GRAPH_MODELS / spec.path).content)
  
  private [rws] def parse(spec: String): GraphModel =
    if(spec == null)
      error("Graph model may not be null.")
    else GraphModelParser.parse(spec)
  
  def compute(document: Elem): GraphModel = new GraphModel(Nil, types(document, Set[NodeType]()).toList, Nil)

  //@todo if the document has not been normalized things like default attributes disappear
  private def types(elem: Seq[Node], nodes: Set[NodeType]): Set[NodeType] = elem.toList match {
    case Text(content) :: tl =>
      types(tl, nodes + NodeType(false, false, AnnotatedIdentifier("_text_", None), List(Identifier("Infom")), List(Attribute(AnnotatedIdentifier("content", None), Some("string"), None))))
    case Elem(_, label, as, _, cs@_*) :: tl =>
      types(tl, types(cs, nodes find (_ match {case NodeType(_, _, AnnotatedIdentifier(name, None), _, _) if label == name => true case _ => false}) match {
        case Some(node) => nodes
      	case None       => nodes + NodeType(false, false, AnnotatedIdentifier(label, None), List(Identifier("Infom")), (as.elements map (a => Attribute(AnnotatedIdentifier(a.key, None), Some("string"), None))).toList)
      }))
    case _  :: tl => types(tl, nodes)
    case Nil      => nodes
  }  
}

/** A graph model.
 *  
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 * 
 *  @todo handle inheritance properly; at the moment we lose the graph model hierarchie
 */
class GraphModel private [locutor] (val name: String, val enums: List[EnumType], val nodes: List[NodeType], val edges: List[EdgeType]) {
  private [locutor] def this(name: String) = this(name, Nil, Nil, Nil)
  
  private [locutor] def this(enums: List[EnumType], nodes: List[NodeType], edges: List[EdgeType]) = this("locutor", enums, nodes, edges)
  
  //@todo scoping
  def join(these: List[GraphModel]): GraphModel = (this /: these)(_ join _)
  
  def join(that: GraphModel): GraphModel = new GraphModel(
    name,
    ((enums.toSet /: that.enums)(_ + _)).toList,
    ((nodes.toSet /: that.nodes)(_ + _)).toList,
    ((edges.toSet /: that.edges)(_ + _)).toList
  )
  
  override def toString = toGrGen
  
  def toGrGen = (enums ::: nodes ::: edges) mkString "\n"  
}
